package com.ozo.cloud.gen.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.generator.util.FileUtils;
import com.ozo.cloud.common.base.constant.CommonFieldConstant;
import com.ozo.cloud.common.core.exception.ServiceException;
import com.ozo.cloud.gen.exception.GenExceptionEnum;
import com.ozo.cloud.gen.pojo.dto.GenTableColumnDetailDTO;
import com.ozo.cloud.gen.pojo.dto.GenTableDetailDTO;
import com.ozo.cloud.gen.service.CodeGeneratorService;
import com.ozo.cloud.gen.util.CodeGenUtil;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModelException;
import jakarta.annotation.PostConstruct;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * <p>
 * 代码生成服务
 * </p>
 *
 * @author qiangesoft
 * @date 2024-11-16
 */
@Service
public class CodeGeneratorServiceImpl implements CodeGeneratorService {

    @Autowired
    private Configuration freemarkerConfiguration;

    @PostConstruct
    public void init() throws TemplateModelException {
        freemarkerConfiguration.setClassForTemplateLoading(this.getClass(), "/templates/");
        // 注册工具类
        freemarkerConfiguration.setSharedVariable("strUtil", new StrUtil());
        freemarkerConfiguration.setDefaultEncoding("UTF-8");
    }

    @Override
    public void generateLocal(GenTableDetailDTO detailDTO) throws IOException, TemplateException {
        String genClassName = StrUtil.lowerFirst(detailDTO.getGenClass());
        String genBusiness = StrUtil.lowerFirst(genClassName.replace(detailDTO.getGenModule(), ""));
        Map<String, Object> templateParam = this.buildParam(detailDTO, genClassName, genBusiness);

        // 代码生成
        Map<String, String> templateMap = CodeGenUtil.TEMPLATE_MAP;
        Map<String, String> filePathMap = CodeGenUtil.filePath(detailDTO.getProjectCode(), detailDTO.getGenPackage(), detailDTO.getGenModule(),
                detailDTO.getGenClass(), genClassName, genBusiness, detailDTO.getGenBackendPath(), detailDTO.getGenFrontPath());

        for (Map.Entry<String, String> entry : filePathMap.entrySet()) {
            String fileKey = entry.getKey();
            String filePath = entry.getValue();

            // 不生成页面及sql
            Boolean genPage = detailDTO.getGenPage();
            if (!genPage) {
                if (CodeGenUtil.JS.equals(fileKey) || CodeGenUtil.INDEX_VUE.equals(fileKey) || CodeGenUtil.SAVE_VUE.equals(fileKey) || CodeGenUtil.SQL.equals(fileKey)) {
                    continue;
                }
            }

            // 不生成新增编辑
            if (!detailDTO.getGenAdd() && !detailDTO.getGenEdit()) {
                if (CodeGenUtil.SAVE_DTO.equals(fileKey)) {
                    continue;
                }
            }

            // 不生成查询
            if (!detailDTO.getGenSelect()) {
                if (CodeGenUtil.VO.equals(fileKey) || CodeGenUtil.QUERY_DTO.equals(fileKey)) {
                    continue;
                }
            }

            String templateFileName = templateMap.get(fileKey);
            Template template = freemarkerConfiguration.getTemplate(templateFileName);

            File file = new File(filePath);
            boolean exist = file.exists();
            if (!exist) {
                File parentFile = file.getParentFile();
                FileUtils.forceMkdir(parentFile);
            }
            try (Writer fileWriter = new FileWriter(filePath)) {
                template.process(templateParam, fileWriter);
            }
        }
    }

    @Override
    public void generateDownload(GenTableDetailDTO detailDTO, HttpServletResponse response) throws IOException, TemplateException {
        String genClassName = StrUtil.lowerFirst(detailDTO.getGenClass());
        String genBusiness = StrUtil.lowerFirst(genClassName.replace(detailDTO.getGenModule(), ""));
        Map<String, Object> templateParam = this.buildParam(detailDTO, genClassName, genBusiness);

        // 代码生成
        Map<String, String> templateMap = CodeGenUtil.TEMPLATE_MAP;
        Map<String, String> filePathMap = CodeGenUtil.zipFileRelativePath(detailDTO.getProjectCode(), detailDTO.getGenPackage(), detailDTO.getGenModule(), detailDTO.getGenClass(), genClassName, genBusiness);

        try {
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream);
            for (Map.Entry<String, String> entry : filePathMap.entrySet()) {
                String fileKey = entry.getKey();
                String filePath = entry.getValue();

                // 不生成页面及sql
                Boolean genPage = detailDTO.getGenPage();
                if (!genPage) {
                    if (CodeGenUtil.JS.equals(fileKey) || CodeGenUtil.INDEX_VUE.equals(fileKey) || CodeGenUtil.SAVE_VUE.equals(fileKey) || CodeGenUtil.SQL.equals(fileKey)) {
                        continue;
                    }
                }

                // 不生成新增编辑
                if (!detailDTO.getGenAdd() && !detailDTO.getGenEdit()) {
                    if (CodeGenUtil.SAVE_DTO.equals(fileKey)) {
                        continue;
                    }
                }

                // 不生成查询
                if (!detailDTO.getGenSelect()) {
                    if (CodeGenUtil.VO.equals(fileKey) || CodeGenUtil.QUERY_DTO.equals(fileKey)) {
                        continue;
                    }
                }

                String templateFileName = templateMap.get(fileKey);
                this.creatZipEntry(templateFileName, filePath, templateParam, zipOutputStream);
            }
            IOUtils.closeQuietly(zipOutputStream);
            byte[] bytes = outputStream.toByteArray();
            response.reset();
            response.setHeader("Content-Disposition", "attachment; filename=code.zip");
            response.addHeader("Content-Length", "" + bytes.length);
            response.setContentType("application/octet-stream; charset=UTF-8");
            IOUtils.write(bytes, response.getOutputStream());
        } catch (Exception e) {
            throw new ServiceException(GenExceptionEnum.CODE_GENERATE_FAIL);
        }
    }

    /**
     * 构建参数
     *
     * @param detailDTO
     * @param genClassName
     * @return
     */
    private Map<String, Object> buildParam(GenTableDetailDTO detailDTO, String genClassName, String genBusiness) {
        String langPkg = "java.lang";
        List<GenTableColumnDetailDTO> columnList = detailDTO.getColumnList();
        List<GenTableColumnDetailDTO> queryFieldList = columnList.stream().filter(GenTableColumnDetailDTO::getPageTableQuery).toList();
        List<String> baseFieldList = Arrays.asList(CommonFieldConstant.BASE_COLUMN);
        Set<String> importPackages = columnList.stream().filter(e -> !e.getJavaTypePackage().startsWith(langPkg)
                        && (!detailDTO.getExtendBaseEntity() || !baseFieldList.contains(e.getJavaField())))
                .map(GenTableColumnDetailDTO::getJavaTypePackage).collect(Collectors.toSet());
        Set<String> savePackages = columnList.stream().filter(e -> !e.getJavaTypePackage().startsWith(langPkg) && e.getPageForm()).map(GenTableColumnDetailDTO::getJavaTypePackage).collect(Collectors.toSet());
        Set<String> queryPackages = columnList.stream().filter(e -> !e.getJavaTypePackage().startsWith(langPkg) && e.getPageTableQuery()).map(GenTableColumnDetailDTO::getJavaTypePackage).collect(Collectors.toSet());
        Set<String> voPackages = columnList.stream().filter(e -> !e.getJavaTypePackage().startsWith(langPkg) && e.getPageTable()).map(GenTableColumnDetailDTO::getJavaTypePackage).collect(Collectors.toSet());

        Map<String, String> javaFilePackage = CodeGenUtil.javaFilePackage(detailDTO.getGenPackage(), detailDTO.getGenModule());

        // 模板参数
        Map<String, Object> templateParam = new HashMap<>();
        templateParam.put("author", detailDTO.getGenAuthor());
        templateParam.put("authorEmail", detailDTO.getGenAuthorEmail());
        templateParam.put("date", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        templateParam.put("version", detailDTO.getGenVersion());
        templateParam.put("tableName", detailDTO.getTableName());
        templateParam.put("module", detailDTO.getGenModule());
        templateParam.put("entity", detailDTO.getGenClass());
        templateParam.put("entityName", genClassName);
        templateParam.put("business", genBusiness);
        templateParam.put("function", detailDTO.getGenFunction());
        templateParam.put("extendBaseEntity", detailDTO.getExtendBaseEntity());
        templateParam.put("baseFields", baseFieldList);
        templateParam.put("package", javaFilePackage);
        templateParam.put("importPackages", importPackages);
        templateParam.put("savePackages", savePackages);
        templateParam.put("queryPackages", queryPackages);
        templateParam.put("voPackages", voPackages);
        templateParam.put("fieldList", columnList);
        templateParam.put("queryFieldList", queryFieldList);
        templateParam.put("genOperateLog", detailDTO.getGenOperateLog());
        templateParam.put("genAdd", detailDTO.getGenAdd());
        templateParam.put("genRemove", detailDTO.getGenRemove());
        templateParam.put("genEdit", detailDTO.getGenEdit());
        templateParam.put("genSelect", detailDTO.getGenSelect());
        templateParam.put("genPage", detailDTO.getGenPage());
        templateParam.put("genPageTableType", detailDTO.getGenPageTableType());
        templateParam.put("genPageFormType", detailDTO.getGenPageFormType());
        templateParam.put("genPageFormOpen", detailDTO.getGenPageFormOpen());
        templateParam.put("parentMenuId", detailDTO.getParentMenuId());
        return templateParam;
    }

    /**
     * 合成单个文件
     *
     * @param templateFileName
     * @param filePath
     * @param templateParam
     * @param zipOutputStream
     */
    private void creatZipEntry(String templateFileName, String filePath, Map<String, Object> templateParam, ZipOutputStream zipOutputStream) throws IOException, TemplateException {
        // 输出流
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        Writer writer = new BufferedWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8));

        // 加载模板并替换变量
        Template template = freemarkerConfiguration.getTemplate(templateFileName);
        template.process(templateParam, writer);
        writer.flush();

        // 文件写入zip
        zipOutputStream.putNextEntry(new ZipEntry(filePath));
        zipOutputStream.write(outputStream.toByteArray());
        zipOutputStream.flush();
        zipOutputStream.closeEntry();
    }

}